use crate::handler::MessageHandler;
use crate::message::{CallReturn, Message};
use crate::messagebus::MessageBus;
use alloc::boxed::Box;
use alloc::format;
use alloc::string::ToString;
use alloc::vec::Vec;
use core::ffi::{c_char, c_int, c_void};
use core::option::Option;
use core::ptr::null_mut;
use embed_std::ffi::{CStr, CString};
use embed_std::{debugln, errorln, infoln};

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct MessageBusCallback {
    pub handle: Option<
        unsafe extern "C" fn(
            topic: *const c_char,
            data: *const c_char,
            data_len: c_int,
            args: *mut c_void,
        ),
    >,
    pub call: Option<
        unsafe extern "C" fn(
            topic: *const c_char,
            method: *const c_char,
            data: *const c_char,
            data_len: c_int,
            args: *mut c_void,
            return_len: *mut c_int,
        ) -> *mut c_char,
    >,
    pub destroy: Option<unsafe extern "C" fn(args: *mut c_void)>,
}

#[derive(Debug)]
pub struct CMessageHandler {
    handler: MessageBusCallback,
    args: *mut libc::c_void,
}

impl CMessageHandler {
    pub fn new(handler: MessageBusCallback, args: *mut libc::c_void) -> Box<dyn MessageHandler> {
        Box::new(CMessageHandler { handler, args })
    }
}

impl MessageHandler for CMessageHandler {
    fn handle(&self, msg: &Message) {
        CString::new(msg.topic()).map(|top| unsafe {
            if let Some(ref handle) = self.handler.handle {
                (handle)(
                    top.as_ptr(),
                    msg.content().as_ptr() as *const libc::c_char,
                    msg.content().len() as libc::c_int,
                    self.args,
                );
            }
        });
    }

    fn call(&self, topic: &str, method: &str, args: &[u8]) -> CallReturn {
        let top = CString::new(topic).map_err(|e| format!("{}", e))?;
        let meth = CString::new(method).map_err(|e| format!("{}", e))?;
        if let Some(call) = self.handler.call {
            let mut data_len = 0i32;
            unsafe {
                let p = (call)(
                    top.as_ptr() as *const libc::c_char,
                    meth.as_ptr() as *const libc::c_char,
                    args.as_ptr() as *const libc::c_char,
                    args.len() as libc::c_int,
                    self.args,
                    &mut data_len,
                );
                let res = if data_len >= 0 {
                    let data = if !p.is_null() && data_len > 0 {
                        let tmp = core::slice::from_raw_parts(p as *mut u8, data_len as usize);
                        Vec::from(tmp)
                    } else {
                        Vec::new()
                    };
                    Ok(data)
                } else {
                    Err("call failed".to_string())
                };
                if !p.is_null() {
                    libc::free(p as *mut c_void);
                }
                res
            }
        } else {
            Err("no handler!".to_string())
        }
    }
}

impl Drop for CMessageHandler {
    fn drop(&mut self) {
        debugln!(
            "CMessageHandler drop handle {:?} args {:p}",
            self.handler,
            self.args
        );
        if let Some(destroy) = self.handler.destroy.take() {
            debugln!("destroy args {:p}", self.args);
            unsafe {
                destroy(self.args);
            }
        }
    }
}

unsafe impl Send for CMessageHandler {}

unsafe impl Sync for CMessageHandler {}

static mut BUS: Option<MessageBus> = None;
static mut LOG_INITED: bool = false;

#[no_mangle]
pub extern "C" fn get_bus() -> *mut libc::c_void {
    bus() as *const MessageBus as *mut libc::c_void
}

#[no_mangle]
pub extern "C" fn init_bus(size: c_int) -> libc::c_int {
    unsafe {
        if BUS.is_none() {
            BUS = Some(MessageBus::new(size as usize));
            0
        } else {
            errorln!("bus has been inited!");
            1
        }
    }
}

pub fn bus() -> &'static MessageBus {
    unsafe {
        if BUS.is_none() {
            BUS = Some(MessageBus::new(100));
        }
        return BUS.as_ref().unwrap();
    }
}

#[no_mangle]
pub extern "C" fn subscribe(
    bus: *mut libc::c_void,
    topic: *const libc::c_char,
    callback: MessageBusCallback,
    args: *mut libc::c_void,
) -> *mut c_char {
    if bus.is_null() {
        return null_mut();
    }
    unsafe {
        let b = &*(bus as *mut MessageBus);
        let handler = CMessageHandler::new(callback, args);
        let token = b.subscribe(
            &CStr::from_ptr(topic).to_string_lossy(),
            Some(handler),
            None,
        );
        let ctoken = libc::malloc(token.len() + 1) as *mut c_char;
        core::ptr::copy(token.as_ptr() as *const c_char, ctoken, token.len());
        *ctoken.offset(token.len() as isize) = 0;
        infoln!("ctoken {:p}", ctoken);
        ctoken
    }
}

#[no_mangle]
pub extern "C" fn unsubscribe_topic(bus: *mut libc::c_void, topic: *const libc::c_char) {
    if bus.is_null() {
        return;
    }
    unsafe {
        let b = &*(bus as *mut MessageBus);
        b.unsubscribe_topic(&CStr::from_ptr(topic).to_string_lossy());
    }
}

#[no_mangle]
pub extern "C" fn unsubscribe(bus: *mut libc::c_void, token: *const libc::c_char) {
    if bus.is_null() {
        return;
    }
    unsafe {
        let b = &*(bus as *mut MessageBus);
        b.unsubscribe(&CStr::from_ptr(token).to_string_lossy());
    }
}

#[no_mangle]
pub extern "C" fn publish(
    bus: *mut libc::c_void,
    topic: *const libc::c_char,
    content: *const libc::c_char,
    content_len: libc::c_int,
    timeout: libc::c_int,
) {
    if bus.is_null() {
        return;
    }
    unsafe {
        let b = &*(bus as *mut MessageBus);
        b.publish(
            &CStr::from_ptr(topic).to_string_lossy(),
            core::slice::from_raw_parts(content as *const u8, content_len as usize),
            timeout as u32,
        );
    }
}

// fn call(&self, topic:&str, method:&str, args:&[u8], to_ms: u32) -> CallReturn
#[no_mangle]
pub extern "C" fn call(
    bus: *mut libc::c_void,
    topic: *const libc::c_char,
    method: *const libc::c_char,
    args: *const libc::c_char,
    args_len: libc::c_int,
    out: *mut *mut c_char,
    data_len: *mut libc::c_int,
    timeout: libc::c_int,
) -> libc::c_int {
    if bus.is_null() {
        return -1;
    }
    unsafe {
        let b = &*(bus as *mut MessageBus);
        let top = CStr::from_ptr(topic).to_string_lossy();
        let meth = CStr::from_ptr(method).to_string_lossy();
        let ret = match b.call(
            &top,
            &meth,
            core::slice::from_raw_parts(args as *const u8, args_len as usize),
            timeout as u32,
        ) {
            Ok(data) => {
                // debug!("call ok");
                if !out.is_null() && data.len() > 0 {
                    let buff = libc::malloc(data.len()) as *mut libc::c_char;
                    core::ptr::copy(data.as_ptr() as *const libc::c_char, buff, data.len());
                    *out = buff;
                }
                if !data_len.is_null() {
                    *data_len = data.len() as i32;
                }
                0
            }
            Err(err) => {
                // debug!("call failed");
                // warn!("call topic {} method {} failed: {}", top, meth, err);
                -1
            }
        };
        ret
    }
}
